package top.xtcoder.jdcbase.base.controller;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;

import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.filepool.FilePool;
import org.nutz.lang.Files;
import org.nutz.lang.util.NutMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import top.xtcoder.jdcbase.base.common.Funs;
import top.xtcoder.jdcbase.base.config.FileUploadConf;
import top.xtcoder.jdcbase.base.core.Resp;
import top.xtcoder.jdcbase.base.entity.system.FileInfo;
import top.xtcoder.jdcbase.base.redis.RedisCache;
import top.xtcoder.jdcbase.base.service.UploadService;
import top.xtcoder.jdcbase.base.service.chunk.FileChunkDTO;
import top.xtcoder.jdcbase.base.service.chunk.FileChunkQP;
import top.xtcoder.jdcbase.base.service.chunk.FileChunkService;
import top.xtcoder.jdcbase.base.service.chunk.FileService;
import top.xtcoder.jdcbase.base.vo.auth.file.MergeInfo;

@Api(tags = "通用文件上传模块")
@RestController
@RequestMapping("/jdcbase/prbase/file/upload")
public class PrBaseUploadFileController {
	private static final Logger log = LoggerFactory.getLogger(PrBaseUploadFileController.class);
	@Autowired
	private FileUploadConf fileUploadConf;
	
	@Autowired
	private UploadService uploadService;
	
	@Autowired
    private FileService fileService;
	
	@Autowired
	private FileChunkService fileChunkService;
	
	@Autowired
	private RedisCache redisCache;
	
	@Autowired
	private Dao dao;
	
	@Autowired
	private FilePool pool;
	
	@SuppressWarnings("unchecked")
	@Order(value = 0)
	@ApiOperation(value="单文件上传方法-不保存到数据库",consumes="multipart/form-data")
	@ApiImplicitParams({
		@ApiImplicitParam(name="file",value="文件流数据",required = true,paramType = "form",dataType = "__File")
	})
	@PostMapping("/singleFiles")
	public Resp<FileInfo> singleFiles(
			@RequestParam(value="file",required = true) MultipartFile file
			,HttpServletRequest request){
		//创建文件在服务器端存放路径
		try {
			String contextType = file.getContentType();
			System.out.println("contextType==" + contextType);
			String fileName = file.getOriginalFilename();
			File tmpFile = pool.createFile(Files.getSuffix(fileName));
			file.transferTo(tmpFile);
			FileInfo fileInfo = uploadService.upload(tmpFile, file.getOriginalFilename(), file.getOriginalFilename(), file.getContentType());
			tmpFile.deleteOnExit();
			
			return Resp.OK("OK", fileInfo);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return Resp.ERROR(e.getMessage());
		} 
	}

	@Order(value = 1)
	@ApiOperation(value="批量文件上传方法-不保存到数据库",consumes="multipart/form-data")
	@ApiImplicitParam(name="files",value="文件流数据",required = true,dataType = "__File",allowMultiple = true)
	@PostMapping("/batchFiles")
	public Resp<List<FileInfo>> batchFiles(MultipartFile[] files, HttpServletRequest request){
		if(files == null|| files.length <= 0) {
			return new Resp().error("文件列表为空");
		}
		try {
			//创建文件在服务器端存放路径
			List<FileInfo> fileInfos = new ArrayList<>();
			for(MultipartFile file : files) {
				String fileName = file.getOriginalFilename();
				File tmpFile = pool.createFile(Files.getSuffix(fileName));
				file.transferTo(tmpFile);
				FileInfo fileInfo = uploadService.upload(tmpFile, file.getOriginalFilename(), file.getOriginalFilename(), file.getContentType());
				fileInfos.add(fileInfo);
				tmpFile.deleteOnExit();
			}
			return Resp.OK("上传成功", fileInfos);
		} catch (Exception e) {
			e.printStackTrace();
			return Resp.ERROR(e.getMessage());
		}
	}
	
	@SuppressWarnings("unchecked")
	@Order(value = 2)
	@ApiOperation(value="单文件上传方法-保存到数据库",consumes="multipart/form-data")
	@ApiImplicitParams({
		@ApiImplicitParam(name="file",value="文件流数据",required = true,paramType = "form",dataType = "__File")
	})
	@PostMapping("/singleFilesSaveDB")
	public Resp<FileInfo> singleFilesSaveDB(
			@RequestParam(value="file",required = true) MultipartFile file
			,HttpServletRequest request){
		//创建文件在服务器端存放路径
		try {
			String fileName = file.getOriginalFilename();
			File tmpFile = pool.createFile(Files.getSuffix(fileName));
			file.transferTo(tmpFile);
			FileInfo fileInfo = uploadService.uploadAndSaveDB(tmpFile, file.getOriginalFilename(), file.getOriginalFilename(), file.getContentType());
			tmpFile.deleteOnExit();
			return Resp.OK("OK", fileInfo);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return Resp.ERROR(e.getMessage());
		} 
	}

	@Order(value = 3)
	@ApiOperation(value="批量文件上传方法-保存到数据库",consumes="multipart/form-data")
	@ApiImplicitParam(name="files",value="文件流数据",required = true,dataType = "__File",allowMultiple = true)
	@PostMapping("/batchFilesSaveDB")
	public Resp<List<FileInfo>> batchFilesSaveDB(MultipartFile[] files, HttpServletRequest request){
		if(files == null|| files.length <= 0) {
			return new Resp().error("文件列表为空");
		}
		try {
			//创建文件在服务器端存放路径
			List<FileInfo> fileInfos = new ArrayList<>();
			for(MultipartFile file : files) {
				String fileName = file.getOriginalFilename();
				File tmpFile = pool.createFile(Files.getSuffix(fileName));
				file.transferTo(tmpFile);
				FileInfo fileInfo = uploadService.uploadAndSaveDB(tmpFile, file.getOriginalFilename(), file.getOriginalFilename(), file.getContentType());
				fileInfos.add(fileInfo);
				tmpFile.deleteOnExit();
			}
			return Resp.OK("上传成功", fileInfos);
		} catch (Exception e) {
			e.printStackTrace();
			return Resp.ERROR(e.getMessage());
		}
	}

	@ApiOperation(value="通过文件id下载文件")
	@GetMapping("/filedown")
	@Order(value = 4)
	public String filedown(@ApiParam("下载文件名 为空默认为上传时候的文件名称")@RequestParam(name="outFileName", defaultValue = "") String outFileName
			,@ApiParam("fileid") @RequestParam String fileid
			,HttpServletRequest request,HttpServletResponse response) {

		if(Funs.isBlank(fileid)) {
			response.reset();
			response.setContentType("text/html");
			response.setHeader("Content-Type", "text/html;charset=UTF-8");
			return "<script>alert('fileid');</script>";
		}
		Cnd cnd = Cnd.where("isdelete", "=", 0).and("id","=",fileid);
		FileInfo f=dao.fetch(FileInfo.class,cnd);
		if(f==null){
			response.reset();
			response.setContentType("text/html");
			response.setHeader("Content-Type", "text/html;charset=UTF-8");
			return "<script>alert('没有找到该记录');</script>";
		}
		String saveFilePath=f.getPath();
		String docTitle=f.getName();
//		String saveFilePath = redicCache.getCacheObject(RedisKey.DOWNLOAD+code);
//		String docTitle = redicCache.getCacheObject(RedisKey.DOWNLOAD+code+":title");
		if(Funs.isBlank(saveFilePath)) {
			response.reset();
			response.setContentType("text/html");
			response.setHeader("Content-Type", "text/html;charset=UTF-8");
			return "<script>alert('鉴权失败，code失效，请重新下载');</script>";
		}
		if(Funs.isNotBlank(docTitle)) {
			outFileName = docTitle;
		}
		String fileSaveName = saveFilePath;
		if(!saveFilePath.startsWith(fileUploadConf.getSavePath())) {
			fileSaveName = fileUploadConf.getSavePath()+saveFilePath;
		}
		File file = new File(fileSaveName);
		if(!file.exists()) {
			response.reset();
			response.setContentType("text/html");
			response.setHeader("Content-Type", "text/html;charset=UTF-8");
			return "<script>alert('下载导出文件失败【文件不存在】，请重新导出');</script>";
		}
		String ext = FileUtil.extName(saveFilePath);
		if(Funs.isBlank(outFileName)) {
			outFileName = "导出-" + Funs.getNowFullTimeNum()+StrUtil.DOT+ext;
		}
		//设置下载头部信息
		Funs.setDownloadHeader(request, response, outFileName);
		//下载处理
		byte[] buff = new byte[1024];
		BufferedInputStream bis = null;
		OutputStream os = null;
		try {
			os = response.getOutputStream();
			bis = new BufferedInputStream(new FileInputStream(file));
			int i = bis.read(buff);
			while (i != -1) {
				os.write(buff, 0, buff.length);
				os.flush();
				i = bis.read(buff);
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (bis != null) {
				try {
					bis.close();
					//file.delete();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return null;
	}

	@ApiOperation(value="文件列表")
	@PostMapping("/filelist")
	@Order(value = 5)
	public Resp<NutMap> filelist(@ApiParam("fileids 文件ID")@RequestBody List<String> fileids
			, HttpServletRequest request, HttpServletResponse response) {
		Cnd cnd = Cnd.where("isdelete", "=", 0);
		SqlExpressionGroup e = Cnd.cri().where().andInStrList("id",fileids);;
		cnd.and(e);
		List<FileInfo> list=dao.query(FileInfo.class,cnd);
		NutMap item = NutMap.NEW();
		item.addv("list",list);
		return Resp.OK("successful",item);
	}
	
	@ApiOperation(value="分片上传 - 追加模式 - 检查是否秒传", notes = "若同一个文件(以md5为准)已经上传过了，则直接返回")
	@GetMapping("/chunk/upload")
	public Resp<Map<String, Object>> checkChunkUpload(@Valid FileChunkQP param) {
		log.info("文件MD5:" + param.getIdentifier());
		List<FileChunkDTO> list = fileChunkService.listByFileMd5(param.getIdentifier());
		Map<String, Object> data = new HashMap<>(1);
		if (list.size() == 0) {
			data.put("uploaded", false);
			return Resp.OK("操作成功", data);
		}
		// 处理单文件
		if (list.get(0).getTotalChunks() == 1) {
			data.put("uploaded", true);
			// todo 返回 url
			data.put("url", "");
			return Resp.OK("操作成功", data);
		}
		// 处理分片
		int[] uploadedFiles = new int[list.size()];
		int index = 0;
		for (FileChunkDTO fileChunkItem : list) {
			uploadedFiles[index] = fileChunkItem.getChunkNumber();
			index++;
		}
		data.put("uploadedChunks", uploadedFiles);
		return Resp.OK("操作成功", data);
	}

	@ApiOperation(value="分片上传 - 追加模式", notes = "后台处理为往一个文件里写入，不用合并")
	@PostMapping("/chunk/upload")
	public Resp<String> chunkUpload(@Valid FileChunkQP param) {
		boolean flag = fileService.uploadFile(param);
		if (!flag) {
			return Resp.ERROR("上传失败");
		}
		return Resp.OK("上传成功");
	}
	
	@ApiOperation(value="分片上传 - 分段模式 - 上传分片的接口", notes = "分段上传，上传完了后需要调用合并合并文件")
	@PostMapping("/slice/upload")
	public Resp<String> sliceUpload(
			@RequestParam(value = "file") 
			@ApiParam("文件信息")
			MultipartFile file,
            
			@RequestParam(value = "hash") 
			@ApiParam("文件哈希值")
			String hash,
            
			@RequestParam(value = "filename") 
			@ApiParam("文件名")
			String filename,
            
			@RequestParam(value = "seq") 
			@ApiParam("分片序号")
			Integer seq,
            
			@RequestParam(value = "type") 
			@ApiParam("文件类型")
			String type) {
		try {
	        return fileChunkService.uploadSlice(file.getBytes(), hash, filename, seq, type);
	    } catch (IOException e) {
	        e.printStackTrace();
	        return Resp.ERROR("上传失败");
	    }
	}
	@ApiOperation(value="分片上传 - 分段模式 - 文件合并接口")
	@PostMapping("/slice/merge")
	public Resp<String> sliceMerge(@RequestBody MergeInfo mergeInfo) {
	    if (mergeInfo!=null) {
	        String filename = mergeInfo.getFilename();
	        String type = mergeInfo.getType();
	        String hash = mergeInfo.getHash();
	        return fileChunkService.uploadMergeSlice(filename, type, hash);
	    }
	    return Resp.ERROR("文件合并失败");
	}
	
	@ApiOperation(value="分片上传 - 分段模式 - 检查极速秒传接口")
	@GetMapping("/slice/check")
	public Resp<String> sliceCheck(@RequestParam(value = "hash") String hash) {
	    return fileChunkService.fastUploadSlice(hash);
	}
}
